Notes On Programming In C
DISCLAIMER: I don't agree with this anymore but I'm leaving it up for historical interest.
See also Rob Pike's version.
Style
(Haters gonna hate)
Do pick one style and use it consistently. It's very awkward to read code which switches between brace styles all the time.
If you work on an existing body of code, use the existing style. Do not introduce another, in particular your own, style.
Do not use type prefixes on your names. Do not use
struct
prefixes on yourstruct
fields. Examples how not to do it:int iFoo; char *pcBar; struct FooBar { int fb_spam, fb_eggs; }
Use
typedef
for yourstruct
s:typedef struct FooBar FooBar;
This is preferable to
typedef struct { ... } FooBar;
because it makes it possible to useFooBar *
pointers inFooBar
.Do not define cute types.
typedef int MyVeryOwnInt;
Consider the following style for functions:
int foobar(char *foo) { }
It allows searching for a function using
/^foobar\(/
.Do not put braces around single statements, e.g. don't do
if(foo){ return 0; }
Use pointers. Seriously, use pointers.
Do not
#include
.c
files. Just don't.Contrary to popular belief, using
goto
will not get you killed by a raptor. You should use it sparingly, but it definitely has its applications.Do not use
const
orrestrict
.const
does not have any effect during runtime.restrict
can introduce extra bugs. Both have no real use.- Many people seem to like
const
claiming that it "prevents bugs". I'm honestly not sure if I have ever come across a single bug that would have been prevented by const. Even if there is the odd examples for which it is true, const is a cancer that spreads through entire codebases as the only sane way to use it is to use it consistently (or else you require casts which could mask real bugs). Considering that it serves little to no purpose it's, in my opinion, just not worth effort. If you do find yourself stuck with code that insists on it, the magic incantation is#define const
.
- Many people seem to like
Casts from and to
void *
are always implicit in C (not in C++). Explicit casts just clutter the code and make you miss out on an opportunity to annoy C++ programmers (in a similar vein try using variable names likenew
,class
ortemplate
).Look at Windows API, glibc header files, Linux kernel source and GNU program source as examples how NOT to write code.
Learn the operator hierarchy. Don't put parentheses where they are not needed. This one takes practice.
if(((((a + b) - c) != d) && (x != (y + z))) || (a > b)) /* is THIS really more readable than */ if(a + b - c != d && x != y + z || a > b) /* this? */
Declare pointers as
int *x
notint* x
becauseint* x, y; int *x, y;
both define
x
, but noty
as a pointer. The second one makes this more obvious. Also if you do intend two pointersint *x, *y
looks better than eitherint* x,* y; int* x, *y;
Avoiding bugs
Do not define functions like
int foobar();
. This is a function with an undefined number of arguments, not a function without arguments as you might expect. Writeint foobar(void);
.Declare all your functions in advance. Using functions without prototypes is a source of bugs.
malloc(3) can fail in various ways. The most straightforward is returning
NULL
, in which case it is a good idea to exit the program. It can also return a seemingly valid address, but your program is killed when you use it.Zero all structures (remember that global and static variables are automatically zeroed). Zero all memory allocated with malloc(3). (suggestion: create a function emalloc which checks for
NULL
and zeroes memory before returning it.)Check for the following errors and abnormalities, even if they do not appear under your configuration:
- partial read(2) (not even an error)
- partial write(2) (an error, unless a signal interrupted the call)
- If your program does signal handling, all blocking syscalls can return prematurely. Be sure to check and compensate for that.
Check for buffer overflows.
Always make sure your strings are NUL-terminated. Beware of functions that might not NUL-terminate your strings, e.g. strncpy(3).
Never ever pass user input to
printf
's first argument or the format argument of any other function.Be aware that the fopen(3) family of functions, printf(3) in particular, are buffered.
Set pointers to
NULL
after calling free(3) on them (unless of course the pointers themselves will be deleted as well).Use
volatile
for machine registers and variables shared between threads.Watch out for integer overflow.
Use the CSP model of threading whenever possible. Using Go instead might be a good idea for thread heavy code.
Complexity
Don't support anything but UTF-8.
Do not localize. English is the STANDARD language.
Portability
Try to avoid using C99 (any newer standard is so full of heresy I refuse to consider them C). In particular:
- Do not use
//
comments. - Declare all local variables right at the beginning of a function.
(Note that Microsoft Visual C does not support these features!) Some features, on the other hand, are so widely supported and useful they are worth using, e.g.
snprintf
.- Do not use
Do not perform arithmetic with
void *
. Some compilers don't like that. (Cast tochar *
first)Do not assume that types like
int
have a particular size. Usestdint.h
'sint32_t
and friends if it matters.Never ever assume a certain endianness. Very much avoid writing endianness dependent code. Example to read an uint32_t into memory: (it is stored in little endian)
uint32_t i; unsigned char buf[4]; read(fd, buf, 4); /* you should check for errors, EOF etc. in real code */ i = buf[0] | buf[1] << 8 | buf[2] << 16 | buf[3] << 24;
There are very few instances where the performance benefit of just reading into
i
directly matters (It definitively won't matter if you're reading from a hard disk).Never ever assume that a
struct
has a certain layout in memory. In particular do not use read(2), write(2) or similar functions directly onstruct
s.Do not cast pointers to integers. Just don't. If you do, make sure it's
uintptr_t
.Avoid using floating point. It can cause portability trouble. A lot of floating point behaviour is undefined. If you do use it, you're well advised to use
double
.Do not use non-blocking I/O. A nice source of portability issues and race conditions if you do.
Do not use expressions with side-effects in complicated ways. Do not assume that the compiler evaluates things between sequence points in a particular order.
a[i++] = i; /* undefined behaviour! */
Do not write to string literals:
char *a; a = "hello, world"; a[0] = 'H'; /* can and will error out */
There is a trick if you want a modifiable string:
char a[] = "hello, world";
Do not use
register
. It most likely does nothing anyway.Never ever use
__attribute__
when usinggcc
. You're doing it wrong.
Performance
- Be aware that string handling in C is unusual. strlen(2) in particular is a O(n) operation. Use pointers.
Preprocessor
Do not use
#define
for integer constants. Useenum
:enum { FOO = 42, BAR = 23, };
Avoid using macros. Really. If you have macros longer than one line, you're probably doing it wrong.
Never ever use
#if
. You're doing it wrong.Avoid using
#ifdef
. It very often is avoidable and only leads to convoluted code like this:#ifdef WINDOWS a = CreateFile(...); #endif #ifdef LINUX a = open(...); #endif #ifdef MACOSX a = open(...); #endif
Better solutions involve concentrating all platform specific code into a single file of which you can have multiple versions. Or in cases like this just using the portable (if ugly) fopen(3) family of functions in the first place.
Just look at cpython's source code if you need more examples why not to use it.
Avoid
#include
in header files. This saves you from the usual#ifndef DICKS
/#define DICKS
/ ... /#endif
dance.Avoid splitting headers in many small files. For most projects, I work with a file
fns.h
containing function prototypes anddat.h
containing everything else.
Really Stupid Shit
Never ever use bitfields (the
int a:4;
thing; if you never heard of it: good for you). And I mean never ever ever ever ever under no circumstances WHATSOEVER. (They are unportable, ugly and useless; just use bit operations).Avoid inline assembly like the plague. There are very few instances where it can be justified. If you need to use assembly for performance or to access machine features, isolate it into a function which can be in a separate
.asm
file You should have and maintain a C version of all "for-performance" assembly code.